// Copyright (C) 2022 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
//
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<广西木子科技有限公司>，授权编号：<NT220402151538>，代码指纹：<A.648883738.885>，技术对接人微信：<ID: Lingmuziyi>】
// 【授权寄送：<收件：李先生、地址：南宁市科园西十路11号国电智能大厦1101F、电话：17736659550、邮箱：yingshashou@vip.qq.com>】
//
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
//
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.x52im.rainbowchat.im;

import com.eva.framework.utils.LoggerFactory;
import com.x52im.rainbowchat.common.dto.cnst.UserProtocalsType;
import com.x52im.rainbowchat.im.dto.CMDBody4AddFriendRequest;
import com.x52im.rainbowchat.im.dto.CMDBody4ProcessFriendRequest;
import com.x52im.rainbowchat.im.impl.ChatLogicManager;
import com.x52im.rainbowchat.im.impl.ChatLogicManager4Group;
import com.x52im.rainbowchat.im.impl.MessageRecordManager;
import com.x52im.rainbowchat.im.impl.OfflineMessageManager;
import com.x52im.rainbowchat.im.impl.UsersStatusManager;
import com.x52im.rainbowchat.im.util.MessageHelper;

import io.netty.channel.Channel;
import net.x52im.mobileimsdk.server.event.ServerEventListener;
import net.x52im.mobileimsdk.server.protocal.Protocal;

/**
 * IM Server 服务器业务事件处理主实现类.
 *
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @version 1.0
 */
public class ChatServerEventListener implements ServerEventListener {
    private static ChatServerEventListener instance = null;

//	private final Gson g = new Gson();

    /**
     * 在线用户管理器
     */
    private UsersStatusManager liverUsersMgr = null;
//	/** 聊天服务逻辑管理器（除群聊天） */
//	private ChatLogicManager chatLogicMgr = null;
//	/** 聊天服务逻辑管理器（群聊专用） */
//	private ChatLogicManager4Group groupChatLogicMgr = null;

    public static ChatServerEventListener getInstance() {
        if (instance == null)
            instance = new ChatServerEventListener();
        return instance;
    }

    private ChatServerEventListener() {
        liverUsersMgr = new UsersStatusManager();
//		chatLogicMgr = new ChatLogicManager();
    }

    /**
     * 用户的IM连接/登陆请求身份验证回调方法定义.
     * <p>
     * 服务端的应用层可在本方法中实现用户登陆验证。
     * <br>
     * 注意：本回调在一种特殊情况下——即用户实际未退出登陆但再次发起来登陆包时，本回调是不会被调用的！
     * <p>
     * 根据MobileIMSDK的算法实现，本方法中用户验证通过（即方法返回值=0时）后
     * ，将立即调用回调方法 {@link #onUserLoginAction_CallBack(int, String, IoSession)}。
     * 否则会将验证结果（本方法返回值错误码通过客户端的 ChatBaseEvent.onLoginMessage(int dwUserId, int dwErrorCode)
     * 方法进行回调）通知客户端）。
     *
     * @param userId  传递过来的准一id，保证唯一就可以通信，可能是登陆用户名、也可能是任意不重复的id等，具体意义由业务层决定
     * @param token   用于身份鉴别和合法性检查的token，它可能是登陆密码，也可能是通过前置单点登陆接口拿到的token等，具体意义由业务层决定
     * @param extra   额外信息字符串。本字段目前为保留字段，供上层应用自行放置需要的内容
     * @param session 此客户端连接对应的会话
     * @return 0 表示登陆验证通过，否则可以返回用户自已定义的错误码，错误码值应为：>=1025的整数
     */
    @Override
    public int onUserLoginVerify(String userId, String token, String extra, Channel session) {
//				try
//				{
//					// 证认成功
//					if(LogicProcessor.userAuthentication(userId, token))
        {
//						userID.setInt(0, id);
//						id++;
//						LoggerFactory.getLog().debug("用户身份验证--用户名："+lpUserName+" 密码："+lpPassword+" 昵称："+lpNickName+"");
            return 0;
        }
//					return 200; // 认证失败，用户名或密码有误
//				}
//				catch (Exception e)
//				{
//					return 200;
//				}
    }

    /**
     * 用户的IM连接/登陆请求验证成功后的回调方法定义（即：上线通知）.
     * <p>
     * 服务端的应用层通常可在本方法中实现用户上线通知等。
     * <br>
     * 注意：本回调在一种特殊情况下——即用户实际未退出登陆但再次发起来登陆包时，回调也是一定会被调用。
     *
     * @param userId  传递过来的准一id，保证唯一就可以通信，可能是登陆用户名、也可能是任意不重复的id等，具体意义由业务层决定
     * @param extra   额外信息字符串。本字段目前为保留字段，供上层应用自行放置需要的内容。为了丰富应用层处理的手段，在本回调中也把此字段传进来了
     * @param session 成功登陆后建立的MINA会话
     */
    @Override
    public void onUserLoginSucess(String userId, String extra, Channel session) {
        LoggerFactory.getLog().info("[RBChat]用户连接IM服务器成功--用户ID:" + userId + ",远程IP:"
                + session.remoteAddress().toString());
        ChatLogicManager.processUserLoginAction(userId, extra, session);
    }

    /**
     * 用户退出IM连接的回调方法定义（即：下线通知）。
     * <p>
     * 服务端的应用层通常可在本方法中实现用户下线通知等。
     *
     * @param userId  下线的用户user_id
     * @param obj
     * @param session 此客户端连接对应的MINA会话
     */
    @Override
    public void onUserLogout(String userId, Object obj, Channel session) {
        LoggerFactory.getLog().info("[RBChat]用户断开/注销IM服务器连接--" + "用户ID:" + userId);
        ChatLogicManager.processUserLogoutAction(userId, obj, session);
    }

    /**
     * 通用数据回调方法定义（客户端发给服务端的（即接收user_id="0"））.
     * <p>
     * MobileIMSDK在收到客户端向user_id=0(即接收目标是服务器)的情况下通过
     * 本方法的回调通知上层。上层通常可在本方法中实现如：添加好友请求等业务实现。
     *
     * @param userId       接收方的user_id（本方法接收的是发给服务端的消息，所以此参数的值肯定==0）
     * @param from_user_id 发送方的user_id
     * @param dataContent  数据内容（文本形式）
     * @param session      此客户端连接对应的MINA会话
     * @return true表示本方法已成功处理完成，否则表示未处理成功。此返回值目前框架中并没有特殊意义，仅作保留吧
     */
    @Override
    public boolean onTransferMessage4C2S(Protocal p, Channel session) {
        // 接收者uid
        String userId = p.getTo();
        // 发送者uid
        String from_user_id = p.getFrom();
        // 消息或指令内容
        String dataContent = p.getDataContent();
        // 消息或指令指纹码（即唯一ID）
        String fingerPrint = p.getFp();
        // 【重要】用户定义的消息或指令协议类型（开发者可据此类型来区分具体的消息或指令）
        int typeu = p.getTypeu();

        LoggerFactory.getLog().debug("[RBChat]收到了客户端" + from_user_id + "的C2S消息：str=" + dataContent);

        // 客户端向userId=0的用户发送消息，即意味着向服务端发送消息,
        // 其实OnTransBuffer_CallBack方法当且仅当客户端向userId=0的用户发送消息时被触发.
        if ("0".equals(userId)) {
            try {
                final String msg = dataContent;
                LoggerFactory.getLog().debug("[RBChat]此消息接收对象为\"0\"(即server自已)。");
                // 处理用户A需要由服务端转发的临时聊天消息（给接收方B）
                if (typeu == UserProtocalsType.MT42_OF_TEMP$CHAT$MSG_A$TO$SERVER) {
                    return ChatLogicManager.processMT42_OF_TEMP$CHAT$MSG_A$TO$SERVER(fingerPrint, msg);
                }
                // 处理用户A需要由服务端转发的群聊消息
                // @since RBChat2.4(2014-03-03)
                else if (typeu == UserProtocalsType.MT44_OF_GROUP$CHAT$MSG_A$TO$SERVER) {
                    ChatLogicManager4Group.processMT44_OF_GROUP$CHAT$MSG_A$TO$SERVER(fingerPrint, msg);
                }
                // 处理用户A发起的添加好友请求
                else if (typeu == UserProtocalsType.MT05_OF_ADD_FRIEND_REQUEST_A$TO$SERVER) {
                    // 解析添加好友请求原数据（客户端A发过来的）
                    CMDBody4AddFriendRequest addFriendMetaFromClient = MessageHelper.pareseAddFriendRequestMessage(dataContent);
                    // 进入处理逻辑并返回结果
                    return ChatLogicManager.processMT05_OF_ADD_FRIEND_REQUEST_A$TO$SERVER(addFriendMetaFromClient);
                }
                // 被添加者【同意】加好友请求的消息（由B发给服务端）
                else if (typeu == UserProtocalsType.MT08_OF_PROCESS_ADD$FRIEND$REQ_B$TO$SERVER_AGREE) {
                    // 解析被添加者同意加好友请求原数据（客户端A发过来的）
                    CMDBody4ProcessFriendRequest processFriendMetaFromClient =
                            MessageHelper.pareseProcessAdd$Friend$Req_B$To$Server_AGREEMessage(dataContent);
                    // 进入处理逻辑并返回结果
                    return ChatLogicManager.processMT08_OF_PROCESS_ADD$FRIEND$REQ_B$TO$SERVER_AGREE(processFriendMetaFromClient);
                }
                // 被添加者【拒绝】加好友请求的消息（由B发给服务端）
                else if (typeu == UserProtocalsType.MT09_OF_PROCESS_ADD$FRIEND$REQ_B$TO$SERVER_REJECT) {
                    // 解析被添加者拒绝加好友请求原数据（客户端A发过来的）
                    CMDBody4ProcessFriendRequest processFriendMetaFromClient =
                            MessageHelper.pareseProcessAdd$Friend$Req_B$To$Server_REJECTMessage(dataContent);
                    // 进入处理逻辑并返回结果
                    ChatLogicManager.processMT09_OF_PROCESS_ADD$FRIEND$REQ_B$TO$SERVER_REJECT(processFriendMetaFromClient);
                }
            } catch (Exception e) {
                LoggerFactory.getLog().warn(e.getMessage(), e);
            }

            //
            return true;
        }

        //
        return false;
    }

    /**
     * 通道数据回调函数定义（客户端发给客户端的（即接收方user_id不为“0”的情况））.
     * <p>
     * <b>注意：</b>本方法当且仅当在数据被服务端成功在线发送出去后被回调调用.
     * <p>
     * 上层通常可在本方法中实现用户聊天信息的收集，以便后期监控分析用户的行为等^_^。
     * <p>
     * 提示：如果开启消息QoS保证，因重传机制，本回调中的消息理论上有重复的可能，请以参数 #fingerPrint
     * 作为消息的唯一标识ID进行去重处理。
     *
     * @param userId       接收方的user_id（本方法接收的是客户端发给客户端的，所以此参数的值肯定>0）
     * @param from_user_id 发送方的user_id
     * @param dataContent
     */
    @Override
    public void onTransferMessage4C2C(Protocal p) {
        // 接收者uid
        String userId = p.getTo();
        // 发送者uid
        String from_user_id = p.getFrom();
        // 消息或指令内容
        String dataContent = p.getDataContent();
        // 消息或指令指纹码（即唯一ID）
        String fingerPrint = p.getFp();
        // 【重要】用户定义的消息或指令协议类型（开发者可据此类型来区分具体的消息或指令）
        int typeu = p.getTypeu();

//		logger.debug("收到了客户端"+from_user_id+"发给客户端"+userId+"的消息：str="+dataContent);

        // 【一对一好友聊天消息回调(可能来自桥接或本实例模式)】
        if (typeu == UserProtocalsType.MT03_OF_CHATTING_MESSAGE) {
            // ** 【用户消息收集】收集用户的聊天消息，以供后台进行用户行为分析，
            MessageRecordManager.colectChattingMsgAsync(dataContent, fingerPrint);
        }

        /******************************* 以下消息回调通知，只可能来自桥接模式 START *************************************/
        /** 因为根据MobileIMSDK APP端服务端器的设计逻辑，S2C消息本实例发送成功时，
         *  由发送者在应用层处理自行决定余下逻辑（如存聊天记录），而不是由框架层的本回调处理 */
        // 【群聊消息回调(只可能来自桥接模式)】
        // 不要在此记录群聊消息（因为群聊消息应只记录发送的，而不记录接收的，
        // 否则1000个群员就得存1000条记录，而本回调就是接收的消息，所以不应在此存）。
        // 群聊消息的聊天记录，应在发送的时候存（谁发谁存，而不是谁收谁存，详见设计备忘录！）。
        else if (typeu == UserProtocalsType.MT45_OF_GROUP$CHAT$MSG_SERVER$TO$B) {// FIXME: 以下代码仅用于Debug时，并无实际业务逻辑，可以删除！

            // 什么也不用做
            LoggerFactory.getLog().debug("[RBChat]【Bridge2C_CallBack-群聊】[typeu=" + p.getTypeu() + "], 客户端"
                    + p.getFrom() + " 发给客户端" + p.getTo() + " 的实时消息：str=[" + dataContent
                    + "] 【根据S2C类型消息记录设计逻辑，此回调中无需存记录，忽略之！】");
        }
        // 【临时/陌生人聊天消息回调(只可能来自桥接模式)】
        // 陌生人消息不要在这里收集记录，根据目前的设计原则，s2c消息记录一律由发送者处理(而不是接收者)，详见设计备忘录
        else if (typeu == UserProtocalsType.MT43_OF_TEMP$CHAT$MSG_SERVER$TO$B) {// FIXME: 以下代码仅用于Debug时，并无实际业务逻辑，可以删除！

            // 什么也不用做
            LoggerFactory.getLog().debug("[RBChat]【Bridge2C_CallBack-陌生人聊】[typeu=" + p.getTypeu() + "], 客户端"
                    + p.getFrom() + " 发给客户端" + p.getTo() + " 的实时消息：str=[" + dataContent
                    + "] 【根据S2C类型消息记录设计逻辑，此回调中无需存记录，忽略之！】");
        }
        /******************************* 以下消息回调通知，只可能来自桥接模式 END *************************************/

        else {
            LoggerFactory.getLog().debug("[RBChat]【C2C_CallBack】[typeu=" + typeu + "], 客户端" + from_user_id + " 发给客户端"
                    + userId + " 的消息：str=[" + dataContent + "] 【不支持的typeu类型，无需进行聊天记录存储！！】");
        }
    }

    /**
     * 服务端在进行消息发送时，当对方在线但实时发送失败、以及其它各种问题导致消息并没能正常发出时
     * ，将无条件走本回调通知。
     * <p>
     * 注意：本方法当且仅当在数据被服务端<u>在线发送</u>失败后被回调调用.
     * <p>
     * <b>此方法存的意义何在？</b><br>
     * 发生此种情况的场景可能是：对方确实不在线（那么此方法里就可以作为离线消息处理了）、
     * 或者在发送时判断对方是在线的但服务端在发送时却没有成功（这种情况就可能是通信错误
     * 或对方非正常通出但尚未到达会话超时时限）。<br><u>应用层在此方法里实现离线消息的处理即可！</u>
     *
     * @param userId       接收方的user_id（本方法接收的是客户端发给客户端的，所以此参数的值肯定>0），此id在本方法中不一定保证有意义
     * @param from_user_id 发送方的user_id
     * @param dataContent  消息内容
     * @param fingerPrint  该消息对应的指纹（如果该消息有QoS保证机制的话），用于在QoS重要机制下服务端离线存储时防止重复存储哦
     * @return true表示应用层已经处理了离线消息（如果该消息有QoS机制，则服务端将代为发送一条伪应答包
     * （伪应答仅意味着不是接收方的实时应答，而只是存储到离线DB中，但在发送方看来也算是被对方收到，只是延
     * 迟收到而已（离线消息嘛））），否则表示应用层没有处理（如果此消息有QoS机制，则发送方在QoS重传机制超时
     * 后报出消息发送失败的提示）
     * @see #onTransferMessage4C2C(int, int, String)
     */
    @Override
    public boolean onTransferMessage_RealTimeSendFaild(Protocal p) {
        return onTransferMessage_RealTimeSendFaild_impl(p, false);
    }

    /**
     * onTransferMessage_RealTimeSendFaild的实现方法。
     *
     * @param p                                  原始数据
     * @param forceFriendChattingNoRecordHistory true表示强制不存储一对一好友聊天的聊天记录，否则存储
     * @return true表示已妥善处理离线消息
     */
    public boolean onTransferMessage_RealTimeSendFaild_impl(Protocal p, boolean forceFriendChattingNoRecordHistory) {
        // 接收者uid
        String userId = p.getTo();
        // 发送者uid
        String from_user_id = p.getFrom();
        // 消息或指令内容
        String dataContent = p.getDataContent();
        // 消息或指令指纹码（即唯一ID）
        String fingerPrint = p.getFp();
        // 【重要】用户定义的消息或指令协议类型（开发者可据此类型来区分具体的消息或指令）
        int typeu = p.getTypeu();

        // 此离线消息是否需要记录到聊天记录中
        boolean needRecordToChattingHistory = false;

        boolean processed = false;
        switch (typeu) {
            // 目前C2C的消息需要离线处理的，也就是用户的一对一好友聊天消息了
            case UserProtocalsType.MT03_OF_CHATTING_MESSAGE: {
//				processed = OfflineMessageManager.processOfflineMessage(p, false);

                // 强制不存储聊天记录（因为一对一好友聊天走的是c2c模式，它的聊天记录在QoS模
                // 式下已走 onTransferMessage4C2C()回调进行了存储，此参数就是为了在QoS场景下
                // 不需要重复存储聊天记录了）
                if (forceFriendChattingNoRecordHistory) {
                    needRecordToChattingHistory = false;
                } else {
                    /** 根据MobileIMSDK和Web端服务端器的设计逻辑，C2C好友聊天消息在接收方离线情况下的聊天记录处理，
                     * 需要在离线回调中处理（而不是在onTransBuffer_C2C_CallBack中），详见“RainbowChat_pro_v4关键设计思路备忘.txt” */
                    needRecordToChattingHistory = true;
                }
//
//				break;
            }

            /******************************* 以下离线处理，只可能来自桥接模式 START *************************************/
            /** 因为根据MobileIMSDK APP端服务端器的设计逻辑，S2C消息本实例发送失败时，由发送者在应用层处理，而不是由框架层的本回调处理 */
            //* 临时聊天消息的离线处理逻辑
            case UserProtocalsType.MT43_OF_TEMP$CHAT$MSG_SERVER$TO$B:
                //* 群组聊天消息的离线处理逻辑
            case UserProtocalsType.MT45_OF_GROUP$CHAT$MSG_SERVER$TO$B:
                //* 各群聊指令通知的离线处理逻辑
            case UserProtocalsType.MT46_OF_GROUP$SYSCMD_MYSELF$BE$INVITE_FROM$SERVER:
            case UserProtocalsType.MT47_OF_GROUP$SYSCMD_COMMON$INFO_FROM$SERVER:
            case UserProtocalsType.MT48_OF_GROUP$SYSCMD_DISMISSED_FROM$SERVER:
            case UserProtocalsType.MT49_OF_GROUP$SYSCMD_YOU$BE$KICKOUT_FROM$SERVER:
            case UserProtocalsType.MT50_OF_GROUP$SYSCMD_SOMEONEB$REMOVED_FROM$SERVER:
            case UserProtocalsType.MT51_OF_GROUP$SYSCMD_GROUP$NAME$CHANGED_FROM$SERVER: {
                processed = OfflineMessageManager.processOfflineMessage(p, false);
                break;
            }
            /******************************* 以上离线处理，只可能来自桥接模式 END *************************************/

            default: {
                LoggerFactory.getLog().debug("[RBChat]【C2C】客户端" + from_user_id + "发给" + userId + "的消息类型typeu为"
                        + typeu + "的消息或指令：str=" + dataContent + "没有在离线处理逻辑内，本次离线回调被忽略哦！");
                processed = true;
                break;
            }
        }

        // 离线消息也要记录到聊天记录中
        if (needRecordToChattingHistory) {
            try {
                // ** 【用户消息收集并落库】收集用户的聊天消息，以供后台进行用户行为分析，
                MessageRecordManager.colectChattingMsgAsync(p.getDataContent(), p.getFp());
            } catch (Exception e) {
                LoggerFactory.getLog().warn(e.getMessage(), e);
            }
        }

        return processed;
    }

    public UsersStatusManager getLiverUsersMgr() {
        return liverUsersMgr;
    }
}
